//-------------------------------------------------------------------
//	Copyright (c) 2010-2011  Zhirnov Andrey
//	This file is part of the "UXGEN_ResourceConverter" project.
//	See copyright notice in "Converter.h".
//-------------------------------------------------------------------

#include "CModelConverter.h"
#include "..\\..\\Core\\CConverter.h"


CModelConverter::CModelConverter():
	_uCashSize(24), _fAccuracy(0.f), _eModelFlags(MDF_NONE), _ePackType(MVP_AUTO), _eType(MT_AUTO),
	_eMeshFlags(MF_NONE), _bRecalcAnimBBoxes(false)
{
}


CModelConverter::~CModelConverter()
{
}


HRESULT CALLBACK CModelConverter::GetSupportedFormats(char *pBuffer, uint uCount)
{
	static const string		s_sFormats = string("model|")
										<< "*.dmd;" << "*.md5mesh;" << "*.obj;"
										<< "*.md5anim;" << "*.3ds;" << "*.dae;"
										<< "*.mdl;" << "*.mdm;" << "*.mds;"
										<< "*.mda;" << "*.mdp";

	if ( pBuffer == nullptr || uCount == 0 )
		return E_INVALIDARG;

	if ( uCount > s_sFormats.Length() )
	{
		memcpy( pBuffer, s_sFormats.cstr(), s_sFormats.Size() );
		return S_OK;
	}
	return S_FALSE;
}


HRESULT CALLBACK CModelConverter::Delete()
{
	_sData.Clear();
	_aAttribs.Clear();
	_sInfo.Clear();
	_eModelFlags	= MDF_NONE;
	_eMeshFlags		= MF_NONE;
	_ePackType		= MVP_AUTO;
	_eType			= MT_AUTO;
	return S_OK;
}

		
HRESULT CALLBACK CModelConverter::GetModelType(E_MODEL_TYPE &eType)
{
	eType = _eType;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::SetModelType(E_MODEL_TYPE eType)
{
	_eType = eType;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::Load(const char *pFileName, E_MODEL_COMPONENT eType)
{
	if ( pFileName == nullptr )
		return E_INVALIDARG;

	if ( !CORE->FileExist( pFileName ) )
		return E_FAIL;

	if ( !_LoadModelComponents( _sData, pFileName, eType ) )
		return E_FAIL;

	return S_OK;
}


HRESULT CALLBACK CModelConverter::Save(const char *pFileName, E_MODEL_COMPONENT eType)
{
	if ( pFileName == nullptr || eType == MC_UNKNOWN )
		return E_INVALIDARG;
	
	IFilePtr	p_file;
	bool		ret		= true;

	if ( CORE->LoadFile( pFileName, p_file, e_file_open_flags::DEF_WRITE ) != S_OK )
		return E_INVALIDARG;

	TMesh			*	p_mesh	= nullptr;
	TMeshAnimation	*	p_anim	= nullptr;
	TMeshMaterial	*	p_mtr	= nullptr;
	TMeshPhysics	*	p_phys	= nullptr;

	if ( EnumCmp( eType, MC_MESH ) )							ret &= _SaveMeshes( p_mesh, p_anim, p_mtr, p_phys, eType );
	if ( EnumCmp( eType, MC_ANIMATION ) && p_anim == nullptr )	ret &= _SaveAnimations( p_anim );
	if ( EnumCmp( eType, MC_MATERIAL )  && p_mtr == nullptr )	ret &= _SaveMaterials( p_mtr );
	if ( EnumCmp( eType, MC_PHYSICS )   && p_phys == nullptr )	ret &= _SavePhysics( p_phys );

	if ( ret )
	{
		ret = Loaders::SaveModel( p_file, p_mesh, p_anim, p_mtr, p_phys );
	}

	return ret ? S_OK : E_FAIL;
}


HRESULT CALLBACK CModelConverter::GetModelInfo(char *pBuffer, uint uSize)
{
	if ( pBuffer == nullptr )
		return E_INVALIDARG;

	if ( uSize >= _sInfo.Length() )
	{
		memcpy( pBuffer, _sInfo.cstr(), uSize );
		pBuffer[uSize] = 0;
	}
	else
	{
		memcpy( pBuffer, _sInfo.cstr(), _sInfo.Length() );
		pBuffer[ _sInfo.Length() ] = 0;
	}
	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetType(E_SUBSYSTEM_TYPE &eType)
{
	eType = SST_MODEL_CONVERTER;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetComponentsCount(E_MODEL_COMPONENT eType, uint &uCount)
{
	switch ( eType )
	{
		case MC_MESH :
			uCount = (uint)_sData.aMeshes.Count();
			break;

		case MC_ANIMATION :
			uCount = (uint)_sData.aAnimations.Count();
			break;

		case MC_MATERIAL :
			uCount = (uint)_sData.aMaterials.Count();
			break;

		case MC_PHYSICS :
			uCount = (uint)_sData.aPhysics.Count();
			break;

		default:
			return E_INVALIDARG;
	};
	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetComponent(uint uIndex, E_MODEL_COMPONENT eType, char *pName, uint uCount)
{
	const string	*p_name = nullptr;

	switch ( eType )
	{
		case MC_MESH :
			if ( uIndex < _sData.aMeshes.Count() )
				p_name = &_sData.aMeshes[uIndex].sName;
			break;

		case MC_ANIMATION :
			if ( uIndex < _sData.aAnimations.Count() )
				p_name = &_sData.aAnimations[uIndex].sName;
			break;

		case MC_MATERIAL :
			if ( uIndex < _sData.aMaterials.Count() )
				p_name = &_sData.aMaterials[uIndex].sName;
			break;

		case MC_PHYSICS :
			if ( uIndex < _sData.aPhysics.Count() )
				p_name = &_sData.aPhysics[uIndex].sName;
			break;

		default:
			return E_INVALIDARG;
	};

	if ( uCount >= p_name->Length() )
	{
		memcpy( pName, p_name->cstr(), uCount );
		pName[uCount] = 0;
	}
	else
	{
		memcpy( pName, p_name->cstr(), p_name->Length() );
		pName[ p_name->Length() ] = 0;
	}

	return S_OK;
}


HRESULT CALLBACK CModelConverter::SetAttribValues(const TMeshVertices *pAttribs, uint uCount)
{
	if ( pAttribs == nullptr || uCount == 0 )
		return E_INVALIDARG;

	_aAttribs.Copy( pAttribs, uCount );
	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetAttribValues(TMeshVertices *pAttribs, uint uCount, uint &uWritten)
{
	if ( pAttribs == nullptr || uCount < _aAttribs.Count() )
		return E_INVALIDARG;

	memcpy( pAttribs, _aAttribs.ptr(), _aAttribs.Size() );
	uWritten = (uint)_aAttribs.Count();

	return S_OK;
}


HRESULT CALLBACK CModelConverter::SetOptimizeParams(uint uCashSize, float fAccuracy, E_MESH_FLAGS eFlags, E_MESH_VERT_PACK ePackType)
{
	_uCashSize	= uCashSize;
	_fAccuracy	= fAccuracy;
	_eMeshFlags	= eFlags;
	_ePackType	= ePackType;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetOptimizeParams(uint &uCashSize, float &fAccuracy, E_MESH_FLAGS &eFlags, E_MESH_VERT_PACK &ePackType)
{
	uCashSize	= _uCashSize;
	fAccuracy	= _fAccuracy;
	eFlags		= _eMeshFlags;
	ePackType	= _ePackType;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::SetSceletalJointBits(uint uAnim, const TSceletalJoint *pJoints, uint uCount)
{
	if ( pJoints == 0 || uAnim >= _sData.aAnimations.Count() )
		return E_INVALIDARG;

	if ( _sData.aAnimations[uAnim].sAnim.eType != TMeshAnimation::SCELETAL )
		return E_INVALIDARG;
	
	TSceletalAnimData	&s_anim = *_sData.aAnimations[uAnim].sAnim.sData.pSceletal;

	if ( uCount != s_anim.aJointInfos.Count() )
		return E_INVALIDARG;

	for (uint i = 0; i < s_anim.aJointInfos.Count(); ++i)
	{
		s_anim.aJointInfos[i].uGroupBits = pJoints[i].uJointGroupBits;
	}

	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetSceletalJointBits(uint uAnim, TSceletalJoint *pJoints, uint uCount, uint &uWritten)
{
	if ( pJoints == 0 || uAnim >= _sData.aAnimations.Count() )
		return E_INVALIDARG;

	if ( _sData.aAnimations[uAnim].sAnim.eType != TMeshAnimation::SCELETAL )
		return E_INVALIDARG;
	
	TSceletalAnimData	&s_anim = *_sData.aAnimations[uAnim].sAnim.sData.pSceletal;

	if ( uCount < s_anim.aJointInfos.Count() )
		return E_INVALIDARG;


	for (uint i = 0; i < s_anim.aJointInfos.Count(); ++i)
	{
		pJoints[i].pJointName		= s_anim.aJointInfos[i].sName.cstr();
		pJoints[i].uJointGroupBits	= s_anim.aJointInfos[i].uGroupBits;
		pJoints[i].uParent			= s_anim.aJointInfos[i].uParent;
	}

	uWritten = (uint)s_anim.aJointInfos.Count();
	return S_OK;
}


HRESULT CALLBACK CModelConverter::SetSceletalAnimTrack(uint uAnim, uint uTrack, const TScelAnimTrack &sParams)
{
	if ( uAnim >= _sData.aAnimations.Count() )
		return E_INVALIDARG;

	if ( _sData.aAnimations[uAnim].sAnim.eType != TMeshAnimation::SCELETAL )
		return E_INVALIDARG;
	
	TSceletalAnimData	&s_anim = *_sData.aAnimations[uAnim].sAnim.sData.pSceletal;

	if ( uTrack >= s_anim.aTracks.Count() )
		return E_INVALIDARG;

	s_anim.aTracks[uTrack].sName		= sParams.pTrackName;
	s_anim.aTracks[uTrack].uGroupMask	= sParams.uGroupMask;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetSceletalAnimTrack(uint uAnim, uint uTrack, TScelAnimTrack &sParams)
{
	if ( uAnim >= _sData.aAnimations.Count() )
		return E_INVALIDARG;

	if ( _sData.aAnimations[uAnim].sAnim.eType != TMeshAnimation::SCELETAL )
		return E_INVALIDARG;
	
	TSceletalAnimData	&s_anim = *_sData.aAnimations[uAnim].sAnim.sData.pSceletal;

	if ( uTrack >= s_anim.aTracks.Count() )
		return E_INVALIDARG;

	sParams.pTrackName	= s_anim.aTracks[uTrack].sName.cstr();
	sParams.uGroupMask	= s_anim.aTracks[uTrack].uGroupMask;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetAnimTracksCount(uint uAnim, uint &uCount)
{
	if ( uAnim >= _sData.aAnimations.Count() )
		return E_INVALIDARG;

	TMeshAnimation	&s_anim = _sData.aAnimations[uAnim].sAnim;

	switch ( s_anim.eType )
	{
		case TMeshAnimation::FRAME :
			uCount = s_anim.sData.pFrameAnim->aTracks.Count();
			return S_OK;

		case TMeshAnimation::SCELETAL :
			uCount = s_anim.sData.pSceletal->aTracks.Count();
			return S_OK;

		/*case TMeshAnimation::MORPHING :
			break;

		case TMeshAnimation::TRANSFORM :
			break;*/

		default :
			return E_FAIL;
	};

	return E_INVALIDARG;
}


bool CModelConverter::_LoadModelComponents(TModelData &sData, const char *pFileName, E_MODEL_COMPONENT eType)
{
	bool	ret = true;

	const char	*p_ext = String::GetFileExt( pFileName );

	if ( p_ext == nullptr ) {
		LOG( "file without extension, can't switch format", );
		return false;
	}

	IGeometryConverter	*p_mc = nullptr;

	// DMD //
	if ( stricmp( p_ext, "dmd" ) == 0 )
		p_mc = new CDMDConverter();
	else
	
	// MD5 //
	if ( stricmp( p_ext, "md5" )    == 0 ||
		 stricmp( p_ext, "md5mesh") == 0 ||
		 stricmp( p_ext, "md5anim") == 0 )
	{
		p_mc = new CMD5Converter();
	}
	else

	// 3DS //
	if ( stricmp( p_ext, "3ds" ) == 0 )
		p_mc = new C3DSConverter();
	else

	// OBJ //
	if ( stricmp( p_ext, "obj" ) == 0 )
		p_mc = new COBJConverter();
	else

	// MDL (UXGEN) //
	if ( stricmp( p_ext, "mdl" ) == 0 ||
		 stricmp( p_ext, "mds" ) == 0 ||
		 stricmp( p_ext, "mda" ) == 0 ||
		 stricmp( p_ext, "mdm" ) == 0 ||
		 stricmp( p_ext, "mdp" ) == 0 )
	{
		p_mc = new CMDLConverter();
	}

	// Unknown //
	else {
		LOG( "unknown file format", );
		return false;
	}


	TModelInfo	s_info( pFileName, eType, _eModelFlags, sData, _sMtrPath, _sTexPath );
	bool		res;

	if ( _eType == MT_AUTO )					res = p_mc->ConvertAutoType( s_info );		else
	if ( _eType == MT_STATIC_MESH )				res = p_mc->ConvertMesh( s_info );			else
	if ( _eType == MT_FRAME_ANIMATION )			res = p_mc->ConvertFrameAnim( s_info );		else
	if ( _eType == MT_SCELETAL_ANIMATION )		res = p_mc->ConvertSceletal( s_info );		else
	//if ( _eType == MT_MORPHING_ANIMATION )		res = p_mc->ConvertMorphing( s_info );		else
	//if ( _eType == MT_TRANSFORM_ANIMATION )		res = p_mc->ConvertTransform( s_info );		else
												res = false;
	delete p_mc;
		
	ret &= res;

	if ( !res ) {
		LOG( (string("error while converting model: \"") << pFileName << "\".").cstr(), );
	}

	return ret;
}


HRESULT CALLBACK CModelConverter::SetMaterialsPath(const char *pPath)
{
	_sMtrPath = pPath;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::SetTexturesPath(const char *pPath)
{
	_sTexPath = pPath;
	return S_OK;
}


bool CModelConverter::_CheckData(const TModelData &sData, E_MODEL_COMPONENT eType)
{
	// check meshes //
	/*if ( !sData.aMeshes.Empty() && EnumCmp( eType, MC_MESH ) )
	{
		const TMesh::E_MESH_TYPE	e_type = sData.aMeshes.GetFront().sMesh.eType;

		for (uint i = 0; i < sData.aMeshes.Count(); ++i)
		{
			const TMesh::E_MESH_TYPE	e_cur_type = sData.aMeshes[i].sMesh.eType;

			if ( e_type == e_cur_type )
				continue;

			if ( e_type     == TMesh::MULTI_MESH &&
				 e_cur_type == TMesh::SCELETAL_MESH )
			{
				continue;
			}

			LOG( "incorrect meshes types", );
			return false;
		}
	}


	// check animations //
	if ( !sData.aAnimations.Empty() && EnumCmp( eType, MC_ANIMATION ) )
	{
		const TMeshAnimation::E_ANIM_TYPE	e_type = sData.aAnimations.GetFront().eType;

		for (uint i = 0; i < sData.aAnimations.Count(); ++i)
		{
			const TMeshAnimation::E_ANIM_TYPE	e_cur_type = sData.aAnimations[i].eType;

			if ( e_type == e_cur_type )
				continue;

			LOG( "incorrect animations types", );
			return false;
		}
	}*/

	return true;
}


HRESULT CALLBACK CModelConverter::Remove(E_MODEL_COMPONENT eType, uint uIndex)
{
	switch ( eType )
	{
		case MC_MESH :
			if ( uIndex < _sData.aMeshes.Count() )
			{
				_sData.aMeshes.Erase( uIndex );
				return S_OK;
			}
			return E_INVALIDARG;


		case MC_ANIMATION :
			if ( uIndex < _sData.aAnimations.Count() )
			{
				_sData.aAnimations.Erase( uIndex );
				return S_OK;
			}
			return E_INVALIDARG;


		case MC_MATERIAL :
			if ( uIndex < _sData.aMaterials.Count() )
			{
				_sData.aMaterials.Erase( uIndex );
				return S_OK;
			}
			return E_INVALIDARG;


		case MC_PHYSICS :
			if ( uIndex < _sData.aPhysics.Count() )
			{
				_sData.aPhysics.Erase( uIndex );
				return S_OK;
			}
			return E_INVALIDARG;


		default:
			return E_INVALIDARG;
	}
}


HRESULT CALLBACK CModelConverter::SetFlags(E_MODEL_FLAGS eFlags)
{
	_eModelFlags = eFlags;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::GetFlags(E_MODEL_FLAGS &eFlags)
{
	eFlags = _eModelFlags;
	return S_OK;
}


HRESULT CALLBACK CModelConverter::ApplyToMesh(uint uIndex)
{
	if ( uIndex >= _sData.aMeshes.Count() )
		return E_INVALIDARG;

	
	// generate tbn //
	if ( EnumCmp( _eModelFlags, MF_GENERATE_TBN ) )
	{
		if ( !GenerateTBN( _sData.aMeshes[uIndex].sMesh,
							EnumCmp( _eModelFlags, MF_GENERATE_TANGENTS ),
							EnumCmp( _eModelFlags, MF_GENERATE_BINORMALS ),
							EnumCmp( _eModelFlags, MF_GENERATE_NORMALS ) ) )
		{
			LOG( "can't generate TBN for mesh", );
		}
	}


	// change vertex type //
	if ( EnumCmp( _eModelFlags, MF_CHANGE_ATTRIBS ) )
	{
		if ( !ChangeVertexAttribs( _sData.aMeshes[uIndex].sMesh, _aAttribs ) ) {
			LOG( "can't change vertex attribs", );
		}
	}


	// calculate bboxes //
	if ( EnumCmp( _eModelFlags, MF_CALC_BOUNDING_BOX ) )
	{
		if ( !CalculateBBoxes( _sData.aMeshes[uIndex].sMesh ) ) {
			LOG( "can't calculate bounding boxes for mesh", );
		}
	}


	// optimize meshes //
	if ( EnumCmp( _eModelFlags, MF_OPTIMIZE_VERTICES ) )
		if ( !OptimizeVertices( _sData.aMeshes[uIndex].sMesh, _fAccuracy, _ePackType ) ) {
			LOG( "can't optimize mesh vertices", );
		}

	if ( EnumCmp( _eModelFlags, MF_OPTIMIZE_INDICES ) )
		if ( !OptimizeIndices( _sData.aMeshes[uIndex].sMesh, _uCashSize ) ) {
			LOG( "can't optimize mesh indices", );
		}

	return S_OK;
}


HRESULT CALLBACK CModelConverter::MergeComponents(E_MODEL_COMPONENT eType)
{
	bool	ret = false;

	switch ( eType )
	{
		case MC_MESH		:	ret = _MergeMeshes();		break;
		case MC_ANIMATION	:	ret = _MergeAnimations();	break;
		case MC_MATERIAL	:	ret = _MergeMaterials();	break;
		case MC_PHYSICS		:	ret = _MergePhysics();		break;
		default				:	return E_INVALIDARG;
	};

	return ret ? S_OK : E_FAIL;
}


HRESULT CALLBACK CModelConverter::RemoveAnimTrack(uint uAnim, uint uTrack)
{
	if ( uAnim >= _sData.aAnimations.Count() )
		return E_INVALIDARG;

	TMeshAnimation	&s_anim = _sData.aAnimations[uAnim].sAnim;

	switch ( s_anim.eType )
	{
		case TMeshAnimation::FRAME :
			{
				TFrameAnimData &	s_frame_anim = *s_anim.sData.pFrameAnim;

				if ( uTrack < s_frame_anim.aTracks.Count() )
				{
					s_frame_anim.aTracks.Erase( uTrack );
					return S_OK;
				}
				break;
			}

		case TMeshAnimation::SCELETAL :
			{
				TSceletalAnimData & s_sceletal = *s_anim.sData.pSceletal;

				if ( uTrack < s_sceletal.aTracks.Count() )
				{
					s_sceletal.aTracks.Erase( uTrack );
					return S_OK;
				}
				break;
			}

		case TMeshAnimation::MORPHING :
			break;

		case TMeshAnimation::TRANSFORM :
			break;

		default :
			return E_FAIL;
	};

	return E_INVALIDARG;
}


bool CModelConverter::_MergeMeshes()
{
	return false;
}


bool CModelConverter::_MergeAnimations()
{
	return false;
}


bool CModelConverter::_MergeMaterials()
{
	return false;
}


bool CModelConverter::_MergePhysics()
{
	return false;
}


bool CModelConverter::_SaveMeshes(TMesh *&pMesh, TMeshAnimation *&pAnim, TMeshMaterial *&pMtr, TMeshPhysics *&pPhys, E_MODEL_COMPONENT eType)
{
	for (usize i = 0; i < _sData.aMeshes.Count(); ++i)
	{
		TMesh	*	p_mesh	= & _sData.aMeshes[i].sMesh;
		bool		res		= false;

		switch ( p_mesh->eType )
		{
			case TMesh::MULTI_MESH :
				if ( _eType == MT_STATIC_MESH || _eType == MT_FRAME_ANIMATION || _eType == MT_AUTO )
				{
					if ( _eType == MT_AUTO ) _eType = MT_FRAME_ANIMATION;
					pMesh = p_mesh;
					res   = true;
				}
				break;

			case TMesh::SCELETAL_MESH :
				if ( _eType == MT_SCELETAL_ANIMATION || _eType == MT_AUTO )
				{
					if ( _eType == MT_AUTO ) _eType = MT_SCELETAL_ANIMATION;
					pMesh = p_mesh;
					res   = true;
				}
				break;
		};

		if ( !res )
			continue;

		if ( EnumCmp( eType, MC_ANIMATION ) )
		{
			const uint	idx = _sData.aMeshes[i].uAnimationIdx;

			if ( idx != -1 )
				pAnim = & _sData.aAnimations[idx].sAnim;
		}

		if ( EnumCmp( eType, MC_MATERIAL ) )
		{
			const uint	idx = _sData.aMeshes[i].uMaterialIdx;

			if ( idx != -1 )
				pMtr = & _sData.aMaterials[idx].sMaterial;
		}

		if ( EnumCmp( eType, MC_PHYSICS ) )
		{
			const uint	idx = _sData.aMeshes[i].uPhysicsIdx;

			if ( idx != -1 )
				pPhys = & _sData.aPhysics[idx].sPhys;
		}
		return true;
	}
	return false;
}


bool CModelConverter::_SaveAnimations(TMeshAnimation *&pAnim)
{
	for (usize i = 0; i < _sData.aAnimations.Count(); ++i)
	{
		switch ( _sData.aAnimations[i].sAnim.eType )
		{
			case TMeshAnimation::FRAME :
				if ( _eType == MT_FRAME_ANIMATION || _eType == MT_AUTO )
				{
					if ( _eType == MT_AUTO ) _eType = MT_FRAME_ANIMATION;
					pAnim = & _sData.aAnimations[i].sAnim;
					return true;
				}
				break;

			case TMeshAnimation::SCELETAL :
				if ( _eType == MT_SCELETAL_ANIMATION || _eType == MT_AUTO )
				{
					if ( _eType == MT_AUTO ) _eType = MT_SCELETAL_ANIMATION;
					pAnim = & _sData.aAnimations[i].sAnim;
					return true;
				}
				break;
		};
	}
	return false;
}


bool CModelConverter::_SaveMaterials(TMeshMaterial *&pMaterial)
{
	if ( _sData.aMaterials.Empty() )
		return false;

	pMaterial = & _sData.aMaterials.Front().sMaterial;
	return true;
}


bool CModelConverter::_SavePhysics(TMeshPhysics *&pPhysics)
{
	return false;
}
